﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Media;

namespace DiscoveryLogic.Analytics.Modules.Diagrams.GravityDiagrams
{
    /// <summary>
    /// springs + particles
    /// </summary>
    public class ParticleSystem
    {
        #region props
        private Vector _Gravity;
        /// <summary></summary>
        public Vector Gravity
        {
            get
            {
                return this._Gravity;
            }
            set
            {
                this._Gravity = value;
            }
        }

        private double _DragFactor = 0.75;
        /// <summary></summary>
        public double DragFactor
        {
            get
            {
                return this._DragFactor;
            }
            set
            {
                this._DragFactor = Math.Min(1, Math.Max(0, value));
            }
        }

        private double _WallFriction = 0.1;
        /// <summary></summary>
        public double WallFriction
        {
            get
            {
                return this._WallFriction;
            }
            set
            {
                this._WallFriction = Math.Min(1, Math.Max(0, value));
            }
        }


        private List<Particle> _Particles;
        /// <summary></summary>
        public List<Particle> Particles
        {
            get
            {
                return this._Particles;
            }
            set
            {
                this._Particles = value;
            }
        }

        private List<Spring> _Springs;
        /// <summary></summary>
        public List<Spring> Springs
        {
            get
            {
                return this._Springs;
            }
            private set
            {
                this._Springs = value;
            }
        }
        #endregion

        #region ctor
        public ParticleSystem()
        {
            this._Particles = new List<Particle>();
            this._Springs = new List<Spring>();
            this._Gravity = new Vector(0.0, 20.0);
        }
        #endregion

        #region render
        public void Render(DrawingContext dc)
        {
            lock (this.Springs)
            {
                foreach (Spring spring in this.Springs)
                {
                    spring.Render(dc);
                }
            }
        }
        #endregion

        #region calc
        public void CalculateDerivative()
        {
            foreach (Particle particle in Particles)
            {
                // Clear all existing forces acting on the particle
                particle.ResetForce();

                // Add a gravity force
                particle.AddForce(Gravity);

                // Add world drag
                Vector drag = particle.Velocity * -this._DragFactor;
                particle.AddForce(drag);
            }

            foreach (Spring spring in Springs)
            {
                // Apply what ever forces this spring holds
                spring.Apply();
            }

            foreach (Particle particle in Particles)
            {
                particle.State = new ParticleState(particle.Velocity, particle.Force * (particle.OneOverMass));
            }
        }

        public int DoEulerStep(double deltaTime, Rect constraintRectangle)
        {
            this.CalculateDerivative();
            int particleMoved = 0;
            foreach (Particle particle in this.Particles)
            {
                particle.State.Position *= deltaTime;
                particle.State.Velocity *= deltaTime;

                particle.Velocity = particle.Velocity + particle.State.Velocity;

                Vector newPosition = particle.Position + particle.State.Position;

                // If the particle is supposed to be constrained to the canvas "visible" area
                // do collision detection and figure out new position and velocity
                if (particle.ConstrainedToConvas && !constraintRectangle.Contains(newPosition.ToPoint()))
                {
                    double x = particle.Velocity.X;
                    double y = particle.Velocity.Y;

                    // If particle is moving left and is to the left of the left canvas boundry
                    // clamp position and reverse velocity and damp velocity by wall friction
                    // This is repeated for each of the four borders.
                    if (particle.Velocity.X < 0 && newPosition.X < constraintRectangle.Left)
                    {
                        newPosition.X = constraintRectangle.Left;
                        x *= -(1.0 - this._WallFriction);
                    }

                    if (particle.Velocity.X > 0 && newPosition.X > constraintRectangle.Right)
                    {
                        newPosition.X = constraintRectangle.Right;
                        x *= -(1.0 - this._WallFriction);
                    }

                    if (particle.Velocity.Y < 0 && newPosition.Y < constraintRectangle.Top)
                    {
                        newPosition.Y = constraintRectangle.Top;
                        y *= -(1.0 - this._WallFriction);
                    }

                    if (particle.Velocity.Y > 0 && newPosition.Y > constraintRectangle.Bottom)
                    {
                        newPosition.Y = constraintRectangle.Bottom;
                        y *= -(1.0 - this._WallFriction);
                    }
                    particle.Velocity = new Vector(x, y);
                }


                if ((particle.Velocity - new Vector()).Length > 20 ||
                    (particle.Position - newPosition).Length > 20)
                    particleMoved++;

                particle.Position = newPosition;
            }

            return particleMoved;
        }
        #endregion
    }
}
